/*
 * Copyright (c) 2025 Li Auto Inc. and its affiliates
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <utility>
#include <cstring>
#include <algorithm>
#include <chrono>
#include <string>

#include <elog/Log.hpp>
#include <deps/common/IPLocator.h>
#include <transport/TransportInterface.h>
#include <transport/TransportFactory.hpp>
#include <random>
#include <filesystem>
#include <sys/stat.h>

#include "UDSDgramTransportInterface.h"
#include "UDSDgramSenderResource.hpp"

using namespace std;
using namespace asio;
using namespace vbs::transport;
using namespace vbsutil::xmlparser;

namespace vbs {
namespace transport {
using SenderResource = vbs::transport::SenderResource;
using SendResourceList = std::vector<std::unique_ptr<SenderResource>>;

TransmitorType UDSDgramTransportDescriptor::get_type() const {
    return TransmitorType::TRANSMITOR_TYPE_UDS;
}

TransportInterface* UDSDgramTransportDescriptor::create_transport() const {
    return new UDSDgramTransportInterface(*this);
}

UDSDgramTransportInterface::~UDSDgramTransportInterface() {}

static inline std::string set_vbs_prefix_for_path(const std::string filename) {
    if (filename.find('/') != std::string::npos) {
        return filename;
    } else {
        std::string new_path = "/dev/socket/vbs/vbs_" + filename;
        return new_path;
    }
}

UDSDgramTransportInterface::UDSDgramTransportInterface(const UDSDgramTransportDescriptor& descriptor)
    : TransportInterface(LOCATOR_KIND_UDS),
      configuration_(descriptor),
      mSendBufferSize(0),
      mReceiveBufferSize(0),
      actual_send_buffer(0),
      actual_recv_buffer(0) {}

bool UDSDgramTransportInterface::init() {
    //default 65535, Support XML customization
    if ((get_configuration()->sendBufferSize != 0) && (get_configuration()->receiveBufferSize != 0)) {
        mSendBufferSize = get_configuration()->sendBufferSize;
        mReceiveBufferSize = get_configuration()->receiveBufferSize;
    } else {
        asio::local::datagram_protocol::socket socket(io_context_init_);
        socket.open(asio::local::datagram_protocol());
        if (get_configuration()->sendBufferSize == 0) {
            socket_base::send_buffer_size option;
            socket.get_option(option);
            get_configuration()->sendBufferSize = static_cast<uint32_t>(option.value());
            mSendBufferSize = get_configuration()->sendBufferSize;
        }
        if (get_configuration()->receiveBufferSize == 0) {
            socket_base::receive_buffer_size option;
            socket.get_option(option);
            get_configuration()->receiveBufferSize = static_cast<uint32_t>(option.value());
            mReceiveBufferSize = get_configuration()->receiveBufferSize;
        }
    }
    return true;
}

bool UDSDgramTransportInterface::IsInputChannelOpen(const Locator& locator) const {
    std::unique_lock<std::recursive_mutex> scopedLock(mInputMapMutex);
    return IsLocatorSupported(locator) &&
           (mInputSockets.find(set_vbs_prefix_for_path(std::string(reinterpret_cast<const char*>(locator.address)))) !=
            mInputSockets.end());
}

bool UDSDgramTransportInterface::IsLocatorSupported(const Locator& locator) const {
    return (locator.kind == LOCATOR_KIND_UDS);
}

bool UDSDgramTransportInterface::is_locator_allowed(const Locator& locator) const {
    return (locator.kind == LOCATOR_KIND_UDS);
}

void UDSDgramTransportInterface::CloseOutputChannel(asio::local::datagram_protocol::socket& socket) {
    socket.cancel();
    socket.close();
}

bool UDSDgramTransportInterface::OpenInputChannel(const Locator& locator, std::shared_ptr<ReceiverResource> receiver,
                                                  uint32_t maxMsgSize) {
    logDebug(TRANSPORT, "create UDS transport channel resource.. maxMsgSize=" << maxMsgSize);
    asio::local::datagram_protocol::socket socket_input(io_context_recv_);
    std::string path = set_vbs_prefix_for_path(std::string(reinterpret_cast<const char*>(locator.address)));
    if (std::remove(path.c_str()) != 0 && errno != ENOENT) {
        elogError(TRANSPORT, RetCode_t::RETCODE_ERROR,
                  "Failed to remove socket file: " << path << ", error: " << strerror(errno));
        return false;
    }
    asio::local::datagram_protocol::endpoint endpoint_input(path);
    socket_input.open(asio::local::datagram_protocol());
    try {
        socket_input.bind(endpoint_input);
    } catch (const std::exception& e) {
        elogError(TRANSPORT, RetCode_t::RETCODE_ERROR, "OpenInputChannel failed: " << e.what());
        return false;
    }

    asio::socket_base::send_buffer_size new_sndbuf(mSendBufferSize);
    asio::socket_base::receive_buffer_size new_rcvbuf(mReceiveBufferSize);
    socket_input.set_option(new_sndbuf);
    socket_input.set_option(new_rcvbuf);

    try {
        mInputSockets[set_vbs_prefix_for_path(std::string(reinterpret_cast<const char*>(locator.address)))] =
            new UDSDgramChannelResource(this, socket_input, maxMsgSize, locator, std::move(receiver));

        std::string path = set_vbs_prefix_for_path(reinterpret_cast<const char*>(locator.address));
        if (path.c_str() != nullptr) {
            if (chmod(path.c_str(), 0666) == -1) {
                elogError(TRANSPORT, RetCode_t::RETCODE_ERROR, "chmod " << path << " Error!");
                return false;
            }
        } else {
            elogError(TRANSPORT, RetCode_t::RETCODE_ERROR, "Invalid socket path!");
            return false;
        }
    } catch (const std::exception& e) {
        (void)e;
        if (CloseInputChannel(locator)) {
            return false;
        } else {
            std::cout << "CloseInputChannel is error~~" << std::endl;
            return false;
        }
    }
    return true;
}

bool UDSDgramTransportInterface::CloseInputChannel(const Locator& locator) {
    UDSDgramChannelResource* channel_resources;
    {
        std::unique_lock<std::recursive_mutex> scopedLock(mInputMapMutex);
        if (!IsInputChannelOpen(locator)) {
            return false;
        }
        // std::string remove_path = std::string(reinterpret_cast<const char*>(locator.address));
        channel_resources =
            mInputSockets.at(set_vbs_prefix_for_path(std::string(reinterpret_cast<const char*>(locator.address))));
        // std::remove(remove_path.c_str());
        (void)mInputSockets.erase(set_vbs_prefix_for_path(std::string(reinterpret_cast<const char*>(locator.address))));
    }
    if (channel_resources != nullptr) {
        channel_resources->disable();
        try {
            channel_resources->release();
        } catch (const asio::system_error& e) {
            logWarning(TRANSPORT, "channel_resources->release() catch :" << e.what());
        }
        channel_resources->clear();
        delete channel_resources;
        channel_resources = nullptr;
    }
    return true;
}

bool UDSDgramTransportInterface::send(octet* send_buffer, uint32_t send_buffer_size,
                                      asio::local::datagram_protocol::socket& socket,
                                      vbsutil::xmlparser::LocatorsIterator* destination_locators_begin,
                                      vbsutil::xmlparser::LocatorsIterator* /*destination_locators_end*/) {
    std::string send_path =
        set_vbs_prefix_for_path(std::string(reinterpret_cast<const char*>((*(*destination_locators_begin)).address)));
    size_t bytesSent = 0;
    asio::local::datagram_protocol::endpoint remote_endpoint_(send_path);
    try {
        asio::error_code ec;
        bytesSent = socket.send_to(asio::buffer(send_buffer, send_buffer_size), remote_endpoint_, 0, ec);
        if (ec) {
            if ((ec == asio::error::would_block) || (ec == asio::error::try_again)) {
                logDebug(TRANSPORT, "UDS send would have blocked. Packet is dropped.");  //reliable try_again?
                return true;
            } else if (ec == asio::error::message_size) {
                logError(TRANSPORT,
                         "send buffer < message size: " << send_buffer_size << ", send failed, ending......");
                return false;
            } else {
                // logError(TRANSPORT, "send failed, erro code: " << ec.value() << ", send failed");
                logDebug(TRANSPORT, "send failed, erro code: " << ec << ", send failed");
                return false;
            }
        }
    } catch (const std::exception& e) {
        // elogError(TRANSPORT, RetCode_t::RETCODE_ERROR, "Send fail. err message: " << e.what());
        return false;
    }
    logDebug(TRANSPORT,
             "UDSDgram: " << bytesSent << " bytes sent to " << send_path << " from " << socket.local_endpoint().path());
    return true;
}

// std::string AppendSuffixToSendPath(const Locator& locator) {
//     std::string path = ;
//     path += UDSDgramMagicWord;
//     return path;
// }

bool UDSDgramTransportInterface::OpenOutputChannel(SendResourceList& sender_resource_list, const Locator& locator_out) {

    if (!IsLocatorSupported(locator_out))
        return false;
    asio::local::datagram_protocol::socket socket_send(io_context_);
    std::string path =
        set_vbs_prefix_for_path(std::string(reinterpret_cast<const char*>(locator_out.address))) + UDSDgramMagicWord;
    if (is_bind_send_socket == false) {  //只允许建一个发送资源。
        if (std::remove(path.c_str()) != 0 && errno != ENOENT) {
            elogError(TRANSPORT, RetCode_t::RETCODE_ERROR,
                      "Failed to remove socket file: " << path << ", error: " << strerror(errno));
            return false;
        }
        is_bind_send_socket = true;
    } else {
        logDebug(TRANSPORT, "OpenOutputChannel more than one: " << set_vbs_prefix_for_path(
                                std::string(reinterpret_cast<const char*>(locator_out.address))));
        return false;
    }
    try {
        getSocketPtr(socket_send)->open(asio::local::datagram_protocol());
        asio::local::datagram_protocol::endpoint endpoint_true(path);
        getSocketPtr(socket_send)->bind(endpoint_true);

        asio::socket_base::send_buffer_size new_sndbuf(mSendBufferSize);
        asio::socket_base::receive_buffer_size new_rcvbuf(mReceiveBufferSize);

        socket_send.set_option(new_sndbuf);
        socket_send.set_option(new_rcvbuf);

        asio::socket_base::send_buffer_size current_sndbuf;
        asio::socket_base::receive_buffer_size current_rcvbuf;

        //set non_block_send, default is true
        socket_send.non_blocking(get_configuration()->non_blocking_send);

        auto timeout = std::chrono::seconds(3);
        auto sec = std::chrono::duration_cast<std::chrono::seconds>(timeout);
        auto usec = std::chrono::duration_cast<std::chrono::microseconds>(timeout - sec);

        struct timeval timeStruct;
        timeStruct.tv_sec = sec.count();
        timeStruct.tv_usec = usec.count();
        (void)setsockopt(getSocketPtr(socket_send)->native_handle(), SOL_SOCKET, SO_SNDTIMEO,
                         reinterpret_cast<const char*>(&timeStruct), sizeof(timeStruct));

        // socklen_t len = sizeof(timeStruct);

        // int ret = getsockopt(getSocketPtr(socket_send)->native_handle(), SOL_SOCKET, SO_SNDTIMEO,
        //                 reinterpret_cast<char*>(&timeStruct), &len);
        // if (ret == 0) {
        //     std::cout << "send time out : "
        //     << timeStruct.tv_sec << " 秒 + "
        //     << timeStruct.tv_usec << " 微秒" << std::endl;
        // } else {
        //     std::cout << "get send time out failed" << std::endl;
        // }

        socket_send.get_option(current_sndbuf);
        socket_send.get_option(current_rcvbuf);

        actual_send_buffer = current_sndbuf.value();
        actual_recv_buffer = current_rcvbuf.value();

        uint32_t messgae_max_size = get_configuration()->maxMessageSize;

        if (actual_send_buffer < messgae_max_size) {
            logDebug(TRANSPORT, "actual_send_buffer (" << actual_send_buffer << ") is smaller than messgae_max_size ("
                                                       << messgae_max_size << ")");
        }
        if (actual_recv_buffer < messgae_max_size) {
            logDebug(TRANSPORT, "actual_recv_buffer (" << actual_recv_buffer << ") is smaller than messgae_max_size ("
                                                       << messgae_max_size << ")");
        }

        sender_resource_list.emplace_back(
            static_cast<SenderResource*>(new UDSDgramSenderResource(*this, socket_send, 65535U)));
    } catch (const std::exception& e) {
        return false;
    }
    return true;
}

bool UDSDgramTransportInterface::DoInputLocatorsMatch(const Locator& left, const Locator& right) const {
    return std::string(reinterpret_cast<const char*>(left.address)) ==
           std::string(reinterpret_cast<const char*>(right.address));
}
}  // namespace transport
}  // namespace vbs
